﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;

namespace NavDBCreator
{
    public partial class Form1 : Form
    {
        Point Offset = new Point();
        RectangleF view;
        bool bShowGrid;

        bool bShowAirports;
        bool bShowNavaidsVOR;
        bool bShowNavaidsNDB;
        bool bShowRunways;
        bool bShowILS;

        bool bShowShorelines;

        [StructLayout(LayoutKind.Sequential)]
        public struct GSHHS
        {
            public int id;
            public int n;
//            public int flag;
            [MarshalAs(UnmanagedType.I1)]
            public byte level;
            [MarshalAs(UnmanagedType.I1)]
            public byte version;
            [MarshalAs(UnmanagedType.I1)]
            public byte greenwich;
            [MarshalAs(UnmanagedType.I1)]
            public byte source_river;

            public int west;
            public int east;
            public int south;
            public int north;
            public int area;
            public int area_full;
            public int containter;
            public int ancestor;
        }

        public class Navaid
        {
            public string ident;
            public string name;

            public float latitude;
            public float longitude;
            public short elevation;

            public int frequency;

            public byte type;/* 1= VOR
                              * 2= VORTAC
                              * 3= TACAN
                              * 4= VOR-DME
                              * 5= NDB
                              * 7= NDB-DME
                              * 9= DME (non-ILS-DME)*/
            public string usage;

            public string country;

            public Navaid(string i,
                string n,
                float lat, float lon, short elev,
                int frequ, byte t, string u, string c)
            {
                ident = i; name = n; latitude = lat; longitude = lon; elevation = elev;
                frequency = frequ; type = t; usage = u; country = c;
            }
        }

        public class ILS
        {
            public string ident;
            public float latitude;
            public float longitude;
            public short elevation;
            public float frequency;
            public float glideslopeangle;
            public float localizercourse;
            public byte crossingheight;
            public byte category;
            public bool hasDME;

            public ILS(string i, float lat, float lon, short el, float freq, float gs, float lc, byte ch, byte cat, bool DME)
            {
                ident = i; latitude = lat; longitude = lon; elevation = el;
                frequency = freq; glideslopeangle = gs; localizercourse = lc;
                crossingheight = ch;
                category = cat;
                hasDME = DME;
            }
        }

        public class Runway
        {
            public string designator;
            public float heading;
            public short length;
            public short width;
            public float latitude;
            public float longitude;
            public short altitude;
            public string surface;

            public Dictionary<int, ILS> ilses = null;

            public Runway(string des, 
                float head, 
                short len, 
                short wid,
                float lat, 
                float lon,
                short alt,
                string surf)
            {
                designator = des; heading = head; length = len;
                width = wid; latitude = lat; longitude = lon;
                altitude = alt; surface = surf;
            }
        }

        public class Airport
        {
            public string icao;
            public string name;
            public float latitude; 
            public float longitude;
            public short altitude;

            public Dictionary<int, Runway> runways = null;

            public Airport(string i, string n, float lat, float lon, short elev)
            {
                icao = i; name = n; latitude = lat; longitude = lon; altitude = elev;
            }
        }

        ndDataSet.AirportsDataTable adt;
        ndDataSet.NavaidsDataTable ndt;
        ndDataSet.RunwaysDataTable rdt;
        ndDataSet.ILSesDataTable idt;
        ndDataSet.NavaidLookupDataTable nldt;
        ndDataSet.NavaidTypesDataTable ntdt;

        static void LowEndianToBigEndian(ref Int32 obj)
        {
            obj= (Int32)(((obj >> 24) & 0x000000ff) |
                   ((obj >> 8) & 0x0000ff00) |
                   ((obj << 8) & 0x00ff0000) |
                   ((obj << 24) & 0xff000000));
        }

        static void ByteArrayToStructure<T>(byte[] bytearray, int relPosInArray, ref T obj)
        {
            int len = Marshal.SizeOf(obj);
            IntPtr i = Marshal.AllocHGlobal(len);
            Marshal.Copy(bytearray, relPosInArray, i, len);
            obj = (T)Marshal.PtrToStructure(i, obj.GetType());
            Marshal.FreeHGlobal(i);
        }

        public Form1()
        {
            InitializeComponent();

            view = new RectangleF(-180.0f, 90.0f, 360.0f, -180.0f);
//            view = new RectangleF(0.0f, 90.0f, 360.0f, -180.0f);

            bShowAirports = false;
            bShowILS = false;
            bShowNavaidsVOR = false;
            bShowNavaidsNDB = false;
            bShowRunways = false;

            bShowShorelines = true;
            //  Load airports
            ndDataSet ds = new ndDataSet();
            airportsTableAdapter1.Fill(ds.Airports);
            navaidsTableAdapter1.Fill(ds.Navaids);
            navaidLookupTableAdapter1.Fill(ds.NavaidLookup);
            navaidTypesTableAdapter1.Fill(ds.NavaidTypes);
            runwaysTableAdapter1.Fill(ds.Runways);
            ilSesTableAdapter1.Fill(ds.ILSes);

            adt = airportsTableAdapter1.GetData();
            ndt = navaidsTableAdapter1.GetData();
            nldt = navaidLookupTableAdapter1.GetData();
            ntdt = navaidTypesTableAdapter1.GetData();
            rdt = runwaysTableAdapter1.GetData();
            idt = ilSesTableAdapter1.GetData();
        }

        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
        }

        private void pictureBox_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.Clear(Color.White);

            if (bShowGrid)
            {
                float fHorzDelta = Math.Abs(view.Width / 10.0f);
                float fVertDelta = Math.Abs(view.Height / 10.0f);

                float fNorth = view.Top;
                if (view.Height < -90.0f)
                {
                    fNorth = (float)Math.Floor(fNorth / 15.0f) * 15.0f;
                    fVertDelta = 15.0f;
                }
                else if (view.Height < -30.0f)
                {
                    fNorth = (float)Math.Floor(fNorth / 5.0f) * 5.0f;
                    fVertDelta = 5.0f;
                }
                else if (view.Height < -10.0f)
                {
                    fNorth = (float)Math.Floor(fNorth / 2.0f) * 2.0f;
                    fVertDelta = 2.0f;
                }
                else if (view.Height < -3.0f)
                {
                    fNorth = (float)Math.Floor(fNorth / 0.5f) * 0.5f;
                    fVertDelta = 0.5f;
                }
                else
                {
                    fNorth = (float)Math.Floor(fNorth / 0.1f) * 0.1f;
                    fVertDelta = 0.1f;
                }

                float fWest = view.Left;
                if (view.Width >= 180.0f)
                {
                    fWest = (float)Math.Floor(fWest / 30.0f) * 30.0f;
                    fHorzDelta = 30.0f;
                }
                else if (view.Width > 90.0f)
                {
                    fWest = (float)Math.Floor(fWest/15.0f)*15.0f;
                    fHorzDelta = 15.0f;
                }
                else if (view.Width > 30.0f)
                {
                    fWest = (float)Math.Floor(fWest / 5.0f) * 5.0f;
                    fHorzDelta = 5.0f;
                }
                else if (view.Width > 10.0f)
                {
                    fWest = (float)Math.Floor(fWest / 2.0f) * 2.0f;
                    fHorzDelta = 2.0f;
                }
                else if (view.Width > 3.0f)
                {
                    fWest = (float)Math.Floor(fWest / .5f) * 0.5f;
                    fHorzDelta = 0.5f;
                }
                else
                {
                    fWest = (float)Math.Floor(fWest / .1f) * 0.1f;
                    fHorzDelta = 0.1f;
                }

                Point pt1, pt2;
                for (float fLon = fWest; fLon < view.Right; fLon += fHorzDelta )
                {
                    pt1 = new Point( LonToScreen(fLon), 0);
                    pt2 = new Point( LonToScreen(fLon), pictureBox.Height);

                    e.Graphics.DrawLine(Pens.LightGray, pt1, pt2);
                }

                for (float fLat = fNorth; fLat > view.Bottom; fLat -= fVertDelta)
                {
                    pt1 = new Point(0, LatToScreen(fLat));
                    pt2 = new Point(pictureBox.Width, LatToScreen(fLat));

                    e.Graphics.DrawLine(Pens.LightGray, pt1, pt2);
                }
            }

            if (bShowAirports)
            {
                for (int i = 0; i < adt.Count; i++)
                {
                    double lat = adt[i].Latitude;
                    double lon = adt[i].Longtitude;

                    if ((lon >= view.Left) && (lon <= view.Right) &&
                        (lat <= view.Top) && (lat >= view.Bottom))
                    {
                        int x = LonToScreen(lon);
                        int y = LatToScreen(lat);

                        e.Graphics.DrawLine(Pens.Black, x, y, x + 1, y);

                        if (view.Width < 5.0)
                        {
                            e.Graphics.DrawString(adt[i].ICAO, new Font("Courier", 8), Brushes.Black, new PointF((float)x, (float)y));
                        }
                    }
                }
            }
            if (bShowNavaidsVOR||bShowNavaidsNDB)
            {
                for (int i = 0; i < ndt.Count; i++)
                {
                    if ((bShowNavaidsNDB && ((ndt[i].Type & 4)==4)) ||
                        (bShowNavaidsVOR && ((ndt[i].Type & 8)==8) ))
                    {
                        double lat = ndt[i].Latitude;
                        double lon = ndt[i].Longtitude;

                        if ((lon >= view.Left) && (lon <= view.Right) &&
                            (lat <= view.Top) && (lat >= view.Bottom))
                        {
                            int x = LonToScreen(lon);
                            int y = LatToScreen(lat);

                            if ((ndt[i].Type & 4) == 4)
                            {
                                e.Graphics.DrawLine(Pens.Green, x, y, x + 1, y);
                            }
                            else
                            {
                                e.Graphics.DrawLine(Pens.Blue, x, y, x + 1, y);
                            }

                            if (view.Width < 5.0)
                            {
                                e.Graphics.DrawString(ndt[i].Ident, new Font("Courier", 8), Brushes.Blue, new PointF((float)x, (float)y));
                            }
                        }
                    }
                }
            }

            if (bShowShorelines)
            {
                using (FileStream stream = new FileStream("..\\..\\ShorelineDB\\gshhs_l.b", FileMode.Open))
//                using (FileStream stream = new FileStream("..\\..\\ShorelineDB\\wdb_borders_l.b", FileMode.Open))
                {
                    GSHHS head = new GSHHS();
                    BinaryReader br = new BinaryReader(stream);

                    while (br.BaseStream.Position < br.BaseStream.Length)
                    {
                        byte[] header = br.ReadBytes(Marshal.SizeOf(head));
                        ByteArrayToStructure(header, 0, ref head);
                        LowEndianToBigEndian(ref head.ancestor);
                        LowEndianToBigEndian(ref head.area);
                        LowEndianToBigEndian(ref head.area_full);
                        LowEndianToBigEndian(ref head.containter);
                        LowEndianToBigEndian(ref head.east);
                        //                    LowEndianToBigEndian(ref head.flag);
                        LowEndianToBigEndian(ref head.id);
                        LowEndianToBigEndian(ref head.n);
                        LowEndianToBigEndian(ref head.north);
                        LowEndianToBigEndian(ref head.south);
                        LowEndianToBigEndian(ref head.west);

                        double fNorth = Convert.ToDouble(head.north) / 1000000.0f;
                        double fSouth = Convert.ToDouble(head.south) / 1000000.0f;
                        double fWest = Convert.ToDouble(head.west) / 1000000.0f;
                        double fEast = Convert.ToDouble(head.east) / 1000000.0f;
                        bool bDrawTwice = false;
                        if (((fWest < 180.0f) && (fEast > 180.0f)) /*|| 
                            ()*/)
                        {
                            bDrawTwice = true;
                        }
                        if (head.level < 2)
                        {
                            Point[] pts = new Point[head.n];
                            Point[] pts2 = null;

                            if (bDrawTwice)
                                pts2 = new Point[head.n];
                            for (int n = 0; n < head.n; n++)
                            {
                                int lon = br.ReadInt32();
                                int lat = br.ReadInt32();

                                LowEndianToBigEndian(ref lat);
                                LowEndianToBigEndian(ref lon);

                                double fLon = Convert.ToDouble(lon) / 1000000.0f;
                                double fLat = Convert.ToDouble(lat) / 1000000.0f;

                                if (!bDrawTwice && (fLon > 180.0f))
                                    fLon -= 360.0f;

                                pts[n].X = LonToScreen(fLon);
                                pts[n].Y = LatToScreen(fLat);

                                if (bDrawTwice)
                                {
                                    pts2[n].X = LonToScreen(fLon - 360.0f);
                                    pts2[n].Y = pts[n].Y;
                                }
                            }
                            e.Graphics.FillPolygon(Brushes.Yellow, pts);

                            if (bDrawTwice)
                                e.Graphics.FillPolygon(Brushes.Yellow, pts2);
//                            e.Graphics.DrawPolygon(Pens.Black, pts);
                        }
                        else
                        {
                            br.BaseStream.Seek(4 * 2 * head.n, SeekOrigin.End);
                        }
                    }
                }

            }
        }

        private int LonToScreen(double fLongitude)
        {
            return (int)((fLongitude - view.Left) / view.Width * (float)pictureBox.Width);
        }

        private double ScreenToLon(int x)
        {
            return ((double)x) / (double)pictureBox.Width * view.Width + view.Left;
        }

        private double ScreenDeltaInLon(int x1, int x2)
        {
            return (double)(x1 - x2) / (double)pictureBox.Width * view.Width;
        }

        private int LatToScreen(double fLatitude)
        {
            return (int)((fLatitude - view.Top) / view.Height * (float)pictureBox.Height);
        }

        private double ScreenToLat(int y)
        {
            return (double)view.Top + ((double)y) / (double)pictureBox.Height * view.Height;
        }

        private double ScreenDeltaInLat(int y1, int y2)
        {
            return (double)(y1 - y2) / (double)pictureBox.Height * view.Height;
        }

        private void AdjustViewRectangle()
        {
            if ((view.Width > 360.0f) || (view.Height < -180.0f))
            {
                view.Location = new PointF(-180.0f, 90.0f);
                view.Size = new SizeF(360.0f, -180.0f);
            }
            if (view.Left < -180.0f)
            {
                view.Location = new PointF(-180.0f, view.Top);
            }
            else if (view.Right > 180.0f)
            {
                view.Location = new PointF(180.0f - view.Size.Width, view.Top);
            }
            if (view.Top > 90.0f)
            {
                view.Location = new PointF(view.Left, 90.0f);
            }
            else if (view.Bottom < -90.0f)
            {
                view.Location = new PointF(view.Left, -90.0f - view.Size.Height);
            }
        }

        private void pictureBox_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.None)
            {
                Offset.X = e.X;
                Offset.Y = e.Y;

                double fLon = ScreenToLon(e.X);
                string fLonDes = fLon < 0.0f ? "W" : "E";
                double fLat = ScreenToLat(e.Y) ;
                string fLatDes = fLat >= 0.0f ? "N" : "S";
                fLon = Math.Abs(fLon);
                fLat = Math.Abs(fLat);
                positionStripLabel.Text = string.Format("Position : {0}{1}  {2}{3}", fLonDes, fLon, fLatDes, fLat);

            }
            else if (e.Button == MouseButtons.Left)
            {
                //  Pan
                float deltaX = 0.0f;
                if (Offset.X != e.X)
                {
                    deltaX = (float)ScreenDeltaInLon(Offset.X , e.X);
                }
                float deltaY = 0.0f;
                if (Offset.Y != e.Y)
                {
                    deltaY = (float)ScreenDeltaInLat(Offset.Y, e.Y);
                }
                view.Location = new PointF(view.Left + deltaX, view.Top + deltaY);
                Offset.X = e.X;
                Offset.Y = e.Y;
                AdjustViewRectangle();
            }
            else if (e.Button == MouseButtons.Right)
            {
                double fZoom = Math.Pow(2.0f, (double)(Offset.Y - e.Y)/20.0f);
                PointF center = new PointF((float)ScreenToLon(e.X), (float)ScreenToLat(e.Y));
                SizeF size= view.Size;
                size.Width /= (float)fZoom;
                size.Height /= (float)fZoom;

                center.X -= (float)size.Width * ((float)e.X / (float)pictureBox.Width);
                center.Y -= (float)size.Height * ((float)e.Y / (float)pictureBox.Height);
                view = new RectangleF(center, size);

                Offset.X = e.X;
                Offset.Y = e.Y;
                AdjustViewRectangle();
            }
        }

        private void showGridToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (bShowGrid)
                bShowGrid = false;
            else
                bShowGrid = true;

            pictureBox.Invalidate();
            pictureBox.Update();
        }

        private void pictureBox_MouseDown(object sender, MouseEventArgs e)
        {
            //  Store relative offset and start the timer
//            Offset.X = e.X;
//            Offset.Y = e.Y;
            updateTimer.Start();
        }

        private void pictureBox_MouseUp(object sender, MouseEventArgs e)
        {
            //  Stop the timer
            updateTimer.Stop();
        }

        private void updateTimer_Tick(object sender, EventArgs e)
        {
            pictureBox.Invalidate();
            pictureBox.Update();      
        }

        private void airportsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (bShowAirports)
                bShowAirports = false;
            else
                bShowAirports = true;

            pictureBox.Invalidate();
            pictureBox.Update();
        }

        private void navaidsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (bShowNavaidsVOR)
                bShowNavaidsVOR = false;
            else
                bShowNavaidsVOR = true;

            pictureBox.Invalidate();
            pictureBox.Update();
        }

        private void runwaysToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (bShowRunways)
                bShowRunways = false;
            else
                bShowRunways = true;

            pictureBox.Invalidate();
            pictureBox.Update();
        }

        private void nDBToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (bShowNavaidsNDB)
                bShowNavaidsNDB = false;
            else
                bShowNavaidsNDB = true;

            pictureBox.Invalidate();
            pictureBox.Update();
        }

        private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
        {

            if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
            {
                string airportFileName = folderBrowserDialog1.SelectedPath + "\\APT.DB";
                string NavaidsFileName = folderBrowserDialog1.SelectedPath + "\\NavAids.DB";

                #region SAVE AIRPORTS DATABASE
                FileStream fs = new FileStream(airportFileName, FileMode.OpenOrCreate);

                BinaryWriter file = new BinaryWriter(fs);
                
                Dictionary<int, Airport> airports = new Dictionary<int,Airport>();
                Dictionary<int, Runway> runways = new Dictionary<int,Runway>();

                //  Export all data to new database struct
                for (int n = 0; n < adt.Count; n++)
                {
                    Airport a = new Airport(adt[n].ICAO, adt[n].Name, (float)adt[n].Longtitude, (float)adt[n].Longtitude, adt[n].Elevation);
                    airports.Add(adt[n].ID, a);
                }

                for (int n = 0; n < rdt.Count; n++)
                {
                    Runway r = new Runway(rdt[n].Ident, rdt[n].TrueHeading, rdt[n].Length, rdt[n].Width, (float)rdt[n].Latitude, (float)rdt[n].Longtitude, rdt[n].Elevation, rdt[n].Surface);

                    runways.Add(rdt[n].ID,r);
                    if (airports[rdt[n].AirportID].runways == null)
                        airports[rdt[n].AirportID].runways = new Dictionary<int, Runway>();
                    airports[rdt[n].AirportID].runways.Add(rdt[n].ID, r);
                }

                for (int n = 0; n < idt.Count; n++)
                {
                    ILS i = new ILS(idt[n].Ident, (float)idt[n].Latitude, (float)idt[n].Longtitude, idt[n].Elevation, idt[n].Freq, idt[n].GsAngle, idt[n].LocCourse, idt[n].CrossingHeight, idt[n].Category, idt[n].HasDme);

                    if (runways[idt[n].RunwayID].ilses == null)
                        runways[idt[n].RunwayID].ilses = new Dictionary<int, ILS>();
                    runways[idt[n].RunwayID].ilses.Add(idt[n].ID, i);
                }

                //  write number of airports first
                file.Write(airports.Count);
                //  write all airports
                foreach (KeyValuePair<int,Airport> ka in airports)
                {
                    file.Write(ka.Value.icao);
                    file.Write(ka.Value.name);
                    file.Write(ka.Value.longitude);
                    file.Write(ka.Value.latitude);
                    file.Write(ka.Value.altitude);

                    if (ka.Value.runways == null)
                        file.Write((int)0);
                    else
                    {
                        file.Write(ka.Value.runways.Count);
                        foreach (KeyValuePair<int, Runway> kr in ka.Value.runways)
                        {
                            file.Write(kr.Value.designator);
                            file.Write(kr.Value.longitude);
                            file.Write(kr.Value.latitude);
                            file.Write(kr.Value.altitude);
                            file.Write(kr.Value.heading);
                            file.Write(kr.Value.length);
                            file.Write(kr.Value.width);
                            file.Write(kr.Value.surface);

                            if (kr.Value.ilses == null)
                                file.Write((int)0);
                            else
                            {
                                file.Write(kr.Value.ilses.Count);
                                foreach (KeyValuePair<int, ILS> ki in kr.Value.ilses)
                                {
                                    file.Write(ki.Value.ident);
                                    file.Write(ki.Value.longitude);
                                    file.Write(ki.Value.latitude);
                                    file.Write(ki.Value.elevation);
                                    file.Write(ki.Value.frequency);
                                    file.Write(ki.Value.localizercourse);
                                    file.Write(ki.Value.glideslopeangle);
                                    file.Write(ki.Value.crossingheight);
                                    file.Write(ki.Value.hasDME);
                                    file.Write(ki.Value.category);
                                }
                            }
                        }
                    }
                }
                #endregion

                #region SAVE NAVAID DATABASE
                FileStream fsnav = new FileStream(NavaidsFileName, FileMode.OpenOrCreate);
                BinaryWriter filenav = new BinaryWriter(fsnav);

                Dictionary<int, Navaid> navaids = new Dictionary<int, Navaid>();

                for (int n = 0; n < ndt.Count; n++)
                {

                    Navaid nav = new Navaid(ndt[n].Ident, ndt[n].Name, (float)ndt[n].Latitude, (float)ndt[n].Longtitude, ndt[n].Elevation, ndt[n].Freq, ndt[n].Type, ndt[n].Usage, "");

                    navaids[ndt[n].ID] = nav;
                }

                for (int n = 0; n < nldt.Count; n++)
                {
                    navaids[nldt[n].ID].country = nldt[n].Country;
                }

                filenav.Write(navaids.Count);
                foreach (KeyValuePair<int, Navaid> kn in navaids)
                {
                    filenav.Write(kn.Value.ident);
                    filenav.Write(kn.Value.name);
                    filenav.Write(kn.Value.longitude);
                    filenav.Write(kn.Value.latitude);
                    filenav.Write(kn.Value.elevation);
                    filenav.Write(kn.Value.frequency);
                    filenav.Write(kn.Value.type);
                    filenav.Write(kn.Value.usage);
                    filenav.Write(kn.Value.country);
                }
                #endregion

            }
        }
    }
}
